package cn.tk.utils;

import java.time.*;
import java.time.format.DateTimeFormatter;
import java.util.Date;
import java.util.OptionalInt;
import java.util.regex.Pattern;
import java.util.stream.IntStream;

/**
 * Created by denglw on 2017-08-25.<br/>
 * Desc: 日期时间工具类，适用JDK8及以上
 */
@SuppressWarnings("unused")
public final class DateTimeUtil {
    public static final String DATE = "yyyy-MM-dd";
    public static final String TIME = "HH:mm:ss";
    public static final String DATE_TIME = "yyyy-MM-dd HH:mm:ss";
    public static final String DATE_TIME_MILLI = "yyyy-MM-dd HH:mm:ss.SSS";
    public static final String DATE_HOUR_MINUTE = "yyyy-MM-dd HH:mm";
    public static final String HOUR_MINUTE = "HH:mm";
    public static final ZoneOffset ZONE8 = ZoneOffset.of("+8");

    private DateTimeUtil() {}

    /**
     * 判断源字符串是否符合yyyy-MM-dd格式.<br/>
     * @param src 源字符串
     * @return boolean
     */
    public static boolean isLocalDate(String src) {
        // yyyy-MM-dd
        String regex = "^[1,2]\\d{3}-(0[1-9]|1[0-2])-(0[1-9]|[1-2][0-9]|3[0-1])$";
        return match(regex, src);
    }

    /**
     * 判断源字符串是否符合HH:mm或者HH:mm:ss格式.<br/>
     * @param src 源字符串
     * @return boolean
     */
    public static boolean isLocalTime(String src) {
        // HH:mm 或者 HH:mm:ss
        String regex = "^(20|21|22|23|[0-1]\\d):[0-5]\\d(:[0-5]\\d)?$";
        return match(regex, src);
    }

    /**
     * 判断源字符串是否符合yyyy-MM-dd HH:mm:ss格式.<br/>
     * @param src 源字符串
     * @return boolean
     */
    public  static boolean isLocalDateTime(String src) {
        // yyyy-M-dd HH:mm:ss 或者 yyyy-MM-dd HH:mm:ss.SSS
        String regex = "^[1-9]\\d{3}-(0[1-9]|1[0-2])-(0[1-9]|[1-2]\\d|3[0-1]) (20|21|22|23|[0-1]\\d):[0-5]\\d:[0-5]\\d(\\.\\d{3})?$";
        return match(regex, src);
    }

    /**
     * 判断日期是否是周末.<br/>
     * @param date 日期
     * @return boolean
     */
    public static boolean isWeekend(final LocalDate date) {
        if (date == null) {
            return false;
        }
        int dayOfWeekValue = date.getDayOfWeek().getValue();
        return dayOfWeekValue == 6 || dayOfWeekValue == 7;
    }

    /**
     * 判断日期是否是周末.<br/>
     * @param date 日期
     * @return boolean
     */
    public static boolean isWeekend(final Date date) {
        if (date == null) {
            return false;
        }
        return isWeekend(toLocalDate(date));
    }

    /**
     * 判断源字符串是否符合日期格式并且是周末.<br/>
     * @param src 源字符串
     * @return boolean
     */
    public static boolean isWeekend(String src) {
        LocalDate date = toLocalDate(src);
        if (date != null) {
            return isWeekend(date);
        }
        LocalDateTime dateTime = toLocalDateTime(src);
        return dateTime != null && isWeekend(dateTime.toLocalDate());
    }

    /**
     * 源字符串转换为日期, 非yyyy-MM-dd格式则返回null.<br/>
     * @param src 源字符串
     * @return localDate
     */
    public static LocalDate toLocalDate(String src) {
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern(DATE);
        boolean match = isLocalDate(src);
        if (!match) {
            return null;
        }
        return LocalDate.parse(src, formatter);
    }

    /**
     * 将Date型日期转换成LocalDate.<br/>
     * @param date 日期
     * @return localDate
     */
    public static LocalDate toLocalDate(Date date) {
        if (date == null) {
            return null;
        }
        return LocalDateTime.ofInstant(date.toInstant(), ZoneId.systemDefault()).toLocalDate();
    }

    /**
     * 源字符串转换成LocalTime, 非HH:mm或者HH:mm:ss则返回null.<br/>
     * @param src 源字符串
     * @return localTime
     */
    public static LocalTime toLocalTime(String src) {
        if (!isLocalTime(src)) {
            return null;
        }
        if (src.length() == 5) {
            return LocalTime.parse(src, DateTimeFormatter.ofPattern(HOUR_MINUTE));
        }
        return LocalTime.parse(src, DateTimeFormatter.ofPattern(TIME));
    }

    /**
     * 获取date的LocalTime.<br/>
     * @param date 日期
     * @return localTime
     */
    public static LocalTime toLocalTime(Date date) {
        if (date == null) {
            return null;
        }
        return toLocalDateTime(date).toLocalTime();
    }

    /**
     * 源字符串转换成localDateTime, 非yyyy-MM-dd HH:mm:ss[.SSS]则返回null.<br/>
     * @param src 源字符串
     * @return localDateTime
     */
    public static LocalDateTime toLocalDateTime(String src) {
        if (!isLocalDateTime(src)) {
            return null;
        }
        if (src.length() == 19) {
            return LocalDateTime.parse(src, DateTimeFormatter.ofPattern(DATE_TIME));
        }
        return LocalDateTime.parse(src, DateTimeFormatter.ofPattern(DATE_TIME_MILLI));
    }

    /**
     * date类型转换成localDateTime类型.<br/>
     * @param date 日期
     * @return localDateTime
     */
    public static LocalDateTime toLocalDateTime(Date date) {
        if (date == null) {
            return null;
        }
        return LocalDateTime.ofInstant(date.toInstant(), ZoneId.systemDefault());
    }

    /**
     * 日期时间转化成字符串序列.<br/>
     * @param dateTime 日期时间
     * @return string
     */
    public static String format(LocalDateTime dateTime) {
        if (dateTime == null) {
            return "";
        }
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern(DATE_TIME);
        return formatter.format(dateTime);
    }

    /**
     * 源字符串是否是日期, 并获取该天的开始时间.<br/>
     * @param src 源字符串
     * @return localDateTime
     */
    public static LocalDateTime startOfDay(String src) {
        LocalDate date = toLocalDate(src);
        if (date != null) {
            return date.atStartOfDay();
        }
        LocalDateTime dateTime = toLocalDateTime(src);
        return startOfDay(dateTime);
    }

    /**
     * 获取指定日期的开始时间.<br/>
     * @param dateTime 日期时间
     * @return localDateTime
     */
    public  static LocalDateTime startOfDay(LocalDateTime dateTime) {
        return dateTime == null ? null : dateTime.toLocalDate().atStartOfDay();
    }

    /**
     * 源字符串是否是日期, 增加指定天数后的开始时间.<br/>
     * @param src 源字符串
     * @param days 增加天数
     * @return localDateTime
     */
    public static LocalDateTime startOfPlusDay(String src, int days) {
        LocalDateTime dateTime = startOfDay(src);
        return dateTime == null ? null : dateTime.plusDays(days);
    }

    /**
     * 获取指定日期增加指定天数后的开始时间.<br/>
     * @param dateTime 日期时间
     * @param days 增加天数
     * @return localDateTime
     */
    public static LocalDateTime startOfPlusDay(LocalDateTime dateTime, int days) {
        if (dateTime == null) {
            return null;
        }
        return startOfDay(dateTime).plusDays(days);
    }

    /**
     * 获取给定日期的周数[1-53].<br/>
     * @param date 日期
     * @param sundayFirst  星期日是否为一周的第一天
     * @return 周数
     */
    public static int weeks(final LocalDate date, boolean sundayFirst) {
        LocalDate firstDate = Year.of(date.getYear()).atDay(1);
        int dayOfWeek = firstDate.getDayOfWeek().getValue();
        int dayOfYear = date.getDayOfYear();
        int weekOfYear;
        if (sundayFirst) {
            weekOfYear = dayOfWeek == 7 ? (dayOfYear + 6) / 7 : (dayOfYear + dayOfWeek + 6) /7;
        } else {
            weekOfYear = dayOfWeek == 1 ? (dayOfYear + 6) / 7 : (dayOfYear + dayOfWeek + 5) / 7;
        }
        return weekOfYear;
    }

    /**
     * 根据星期数[1-7]获取对应的中文描述.<br/>
     * @param weekNum 星期数 1-7
     * @param prefix 前缀
     * @return string
     */
    public static String weekDesc(int weekNum, String prefix) {
        String[] desc= {"一", "二", "三", "四", "五", "六", "日"};
        OptionalInt optional = IntStream.rangeClosed(1, 7).filter(i -> i == weekNum).findFirst();
        return optional.isPresent() ? prefix + desc[optional.getAsInt() - 1] : "";
    }

    /**
     * 正则匹配.<br/>
     */
    private static boolean match(String regex, String source) {
        if (regex == null || source == null) {
            return false;
        }
        return Pattern.matches(regex, source);
    }

}
